/*题意：给定一棵N个结点的无向树T，要求选择最少的结点使得所有的树边都被覆盖，一个结点可以覆盖所有的邻接边。

思路：树型DP。

数据结构：邻接表

状态表示：now,next均为结点，在树T中,next为now的子结点，即now为next的父亲结点；

          dp[now][0]表示当now不被入选时，以now为树根的树所需放置的最少节点；

          dp[now][1]表示当now入选时，以now为树根的树所需旋转的最少节点。

状态转移：dp[now][0] +＝ dp[next][1]; （表示now结点没有入选，则它所有的子结点必须入选）

          dp[now][1] += min(dp[next][0], dp[next][1])（表示now结点入选了，则累加它所有子树所需的结点数）

最终结果为ans = min(dp[root][0], dp[root][1]);

源代码：
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAXN = 1501;
struct Edge {
     int v;
     Edge *next;
}*adj[MAXN], memo[2 * MAXN];
int mSet, dp[MAXN][3];
void addEdge(int u, int v) {
     Edge *ptr = &memo[mSet ++];
     ptr -> v = v;
     ptr -> next = adj[u];
     adj[u] = ptr;
}
void dfs(int now, int prev) {
     for(Edge *ptr = adj[now]; ptr; ptr = ptr -> next) {
          int v = ptr -> v;
          if(v != prev) {
               dfs(v, now);
               dp[now][0] += dp[v][1];
               dp[now][1] += min(dp[v][0], dp[v][1]);
          }
     }
     dp[now][1] ++;
}
int main() {
     int i, j, m, n, u, v;
     while(scanf("%d", &n) != EOF) {
          for(i = 0; i < n; i ++) {
               adj[i] = NULL;
          }
          mSet = 0;
          for(i = 0; i < n; i ++) {
               scanf("%d:(%d)", &u, &m);
               for(j = 0; j < m; j ++) {
                    scanf("%d", &v);
                    addEdge(u, v);
                    addEdge(v, u);
               }
          }
          memset(dp, 0, sizeof(dp));
          dfs(0, 0);
          printf("%d\n", min(dp[0][0], dp[0][1]));
     }
     return(0);
}

